package fr.castorflex.openharmony.circularprogressbar;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.utils.Color;
import org.jetbrains.annotations.NotNull;

class DefaultDelegate implements PBDelegate {
    private static final long ROTATION_ANIMATOR_DURATION = 2000;
    private static final long SWEEP_ANIMATOR_DURATION = 600;
    private static final float MIN_SWEEP_ANGLE = 30;
    private AnimatorValue mSweepAppearingAnimator;
    private AnimatorValue mRotationAnimator;
    private AnimatorValue mEndAnimator;
    private boolean mModeAppearing;
    private int mCurrentColor;
    private int mCurrentIndexColorAppearing;
    private int mCurrentIndexColorDisappearing;
    private float mCurrentSweepAngle;
    private float mCurrentRotationAngleOffset = 0;
    private float mCurrentRotationAngle = 0;
    private float mCurrentEndRatio = 1f;
    private boolean mFirstSweepAnimation;
    private float mCurrentGlobalAngleOffset;
    private float mCurrentGlobalAngle;
    private final int[] mColors;
    private float mSweepSpeed;
    private float mRotationSpeed;
    private final int mMinSweepAngle;
    private final int mMaxSweepAngle;

    private final CircularProgressDrawable mParent;
    private CircularProgressDrawable.OnEndListener mOnEndListener;

    DefaultDelegate(@NotNull CircularProgressDrawable parent,
                    @NotNull Options options) {
        mParent = parent;
        mCurrentIndexColorAppearing = 0;
        mColors = options.colors;
        mCurrentIndexColorDisappearing = mColors.length;
        mCurrentColor = mColors[0];
        mSweepSpeed = options.sweepSpeed;
        mRotationSpeed = options.rotationSpeed;
        mMinSweepAngle = options.minSweepAngle;
        mMaxSweepAngle = options.maxSweepAngle;

        setupAnimations();
    }

    private void reinitValues() {
        mFirstSweepAnimation = true;
        mCurrentEndRatio = 1f;
        mParent.getCurrentPaint().setColor(new Color(mCurrentColor));
    }

    @Override
    public void draw(Canvas canvas, Paint paint) {
        float startAngle = mCurrentGlobalAngle - mCurrentGlobalAngleOffset;
        float sweepAngle = mCurrentSweepAngle;
        if (!mModeAppearing) {
            startAngle = startAngle + sweepAngle;
            sweepAngle = 360 - sweepAngle - MIN_SWEEP_ANGLE;
        } else {
            sweepAngle += MIN_SWEEP_ANGLE;
        }
        canvas.drawArc(mParent.getDrawableBounds(), new Arc(startAngle, sweepAngle, false), paint);
    }

    public void setSpeed(float speed) {
        if (speed < 0) throw new IllegalArgumentException("Speed must be >= 0");
        mSweepSpeed = speed;
        mParent.invalidateSelf();
    }

    public void setRotation(float speed) {
        if (speed < 0) throw new IllegalArgumentException("Speed must be >= 0");
        mRotationSpeed = speed;
        mParent.invalidateSelf();
    }

    @Override
    public void start() {
        mRotationAnimator.start();
        mSweepAppearingAnimator.start();
        mParent.invalidateSelf();
    }

    @Override
    public void stop() {
        stopAnimators();
    }

    private void stopAnimators() {
        mRotationAnimator.cancel();
        mSweepAppearingAnimator.cancel();
        mParent.invalidateSelf();
    }

    private void setAppearing() {
        mModeAppearing = true;
        mCurrentRotationAngleOffset += mMinSweepAngle;
    }

    private void setDisappearing() {
        mModeAppearing = false;
        mCurrentRotationAngleOffset = mCurrentRotationAngleOffset + (360 - mMaxSweepAngle);
    }

    private void setCurrentRotationAngle(float currentRotationAngle) {
        mCurrentRotationAngle = currentRotationAngle;
        mParent.invalidateSelf();
    }

    private void setCurrentSweepAngle(float currentSweepAngle) {
        mCurrentSweepAngle = currentSweepAngle;
        mParent.invalidateSelf();
    }

    private void setEndRatio(float ratio) {
        mCurrentEndRatio = ratio;
        mParent.invalidateSelf();
    }

    //////////////////////////////////////////////////////////////////////////////
    ////////////////            Animation

    private void setupAnimations() {
        mRotationAnimator = new AnimatorValue();
        mRotationAnimator.setCurveType(Animator.CurveType.LINEAR);
        mRotationAnimator.setDuration((long) (ROTATION_ANIMATOR_DURATION / mRotationSpeed));
        mRotationAnimator.setLoopedCount(-1);
        mRotationAnimator.setValueUpdateListener((animatorValue, v) -> {
            mCurrentGlobalAngle = v * 360f;
            mParent.invalidateSelf();
        });
        mSweepAppearingAnimator = new AnimatorValue();
        mSweepAppearingAnimator.setCurveType(Animator.CurveType.DECELERATE);
        mSweepAppearingAnimator.setDuration((long) (SWEEP_ANIMATOR_DURATION / mSweepSpeed));
        mSweepAppearingAnimator.setLoopedCount(-1);
        mSweepAppearingAnimator.setValueUpdateListener((animatorValue, v) -> mCurrentSweepAngle = (360f - MIN_SWEEP_ANGLE * 2) * v);
        mSweepAppearingAnimator.setLoopedListener(animator -> toggleAppearingMode());
    }

    private void toggleAppearingMode() {
        mModeAppearing = !mModeAppearing;
        if (mModeAppearing) {
            if (mCurrentIndexColorAppearing == mColors.length) {
                mCurrentIndexColorAppearing = 0;
            }
            int nextColor = mColors[mCurrentIndexColorAppearing];
            mParent.getCurrentPaint().setColor(new Color(nextColor));
            mCurrentIndexColorAppearing++;
            mCurrentGlobalAngleOffset = (mCurrentGlobalAngleOffset + MIN_SWEEP_ANGLE * 2) % 360;
        } else {
            if (mCurrentIndexColorDisappearing == 0) {
                mCurrentIndexColorDisappearing = mColors.length;
            }
            int nextColor = mColors[mCurrentIndexColorDisappearing - 1];
            mCurrentIndexColorDisappearing--;
            mParent.getCurrentPaint().setColor(new Color(nextColor));
        }
    }

    /////////////////////////////////////////////////////////
    /// Stop
    /////////////////////////////////////////////////////////

    @Override
    public void progressiveStop(CircularProgressDrawable.OnEndListener listener) {
        mOnEndListener = listener;
        mEndAnimator.setStateChangedListener(new SimpleAnimatorListener() {

            @Override
            public void onPreAnimationEnd(Animator animation) {
                mEndAnimator.release();
                CircularProgressDrawable.OnEndListener endListener = mOnEndListener;
                mOnEndListener = null;

                if (isStartedAndNotCancelled()) {
                    setEndRatio(0f);
                    mParent.stop();
                    if (endListener != null) {
                        endListener.onEnd(mParent);
                    }
                }
            }
        });
        mEndAnimator.start();
    }
}
